/**
*
*	clip_manager.c - Modulo para gestionar clipboards externos
*
*/

#include "clip_manager.h"

/* Licencia del modulo */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alvaro Perez Liaño - Xavier Gallofre Nieva");

/* Estructura de los ficheros en /proc */
static struct proc_dir_entry *proc_activation;
static struct proc_dir_entry *proc_desactivation;
static struct proc_dir_entry *proc_monitor;

/* Estructura de los directorios en /proc */
struct proc_dir_entry *proc_dir_aiso;
// Exportamos el struct para poder accederlo desde fuera
EXPORT_SYMBOL(proc_dir_aiso);

/* Estructura que simula un padre ficticio para las llamadas a funciones
   de creacion de directorios/entradas sin padre (NULL) */
static struct proc_dir_entry *fake_parent = NULL;

/* struct de cada nodo de la lista */
struct clipstruct {
	char *name;
	struct list_head list;
};

/* Lista de clipboards */
LIST_HEAD (list_clipstruct);
/* Numero de drivers cargados - Lo usamos para reservar memoria en la lectura
de la entrada "monitor" */
static int num_clips;

/* Variables de cadenas para la lectura de buffers */
char *my_buffer, *clip_name;

/* Parametros y variables de entorno de user mode helper */
static char *envp[]	= {
		"HOME=/",
		"TERM=linux",
		"PATH=/sbin:/bin:/usr/sbin:/usr/bin",
		NULL 
	};

/**************************** INICIO FUNCIONES LECTURA/ESCRITURA *******************************/ 
/* Funcion de lectura del clipboard que monitoriza los clipboard activos */
int read_proc_monitor(char *buffer, char **buffer_location, off_t offset, int buffer_length, int *eof, void *data)
{
	struct list_head *pos;
	struct clipstruct *obj;
	char *return_list;
	int len = 0;
	if (offset > 0) {
		return 0;
	} else {
		// sizeof(char)*256*NAME_CLIP_LEN - Reservamos NAME_CLIP_LEN caracteres por cada clipboard en la lista
		return_list = (char *) vmalloc((NAME_CLIP_LEN+1)*num_clips);
		list_for_each(pos, &list_clipstruct) {
			obj = list_entry(pos, struct clipstruct, list);
			strcat(return_list,obj->name);
			strcat(return_list,"\n");
			len = len + strlen(obj->name);
		}
		//printk(KERN_ALERT "%s\n",return_list);
		//printk(KERN_ALERT "%i\n",len);
		memcpy(buffer, return_list, len);
		vfree(return_list);
		return len;	
	}
}

/* Funcion de lectura vacia para evitar mensajes de error */
int read_proc_empty(char *buffer, char **buffer_location, off_t offset, int buffer_length, int *eof, void *data)
{
	return 0;
}

/* Funcion de escritura de la entrada "selection" */
/*
Los modulos se cargan desde /lib/modules/2.6...
Ejecutar depmod -a antes
*/
int write_proc_activation(struct file *filepointer, const char __user *buffer, unsigned long len, void *data)
{
	int i,length;
	if (len > NAME_CLIP_LEN) {
		i = NAME_CLIP_LEN;
	} else {
		i = len;
	}
	/* Leemos del usuario */	
	if (copy_from_user(my_buffer, buffer, i)) {
		printk(KERN_ALERT "Imposible escribir\n");
		return -EFAULT;
	}
	// Damos formato a la cadena
	sscanf(my_buffer, "%s", clip_name);
	// Quitamos el caracter '\n' en caso de haberlo
	if (clip_name != NULL) {
		length = strlen(clip_name);
		if (clip_name[length - 1] == '\n')
			clip_name[length - 1] = '\0';
	}
	/* Comprobamos que el modulo no esté ya cargado, si no lo está lo añadimos a la lista */
	if (find_module_in_list(&clip_name) != 0) {
		/* Ejecutamos la instruccion con el user mode helper */
		add_module_custom(&clip_name);
	} else {
		printk(KERN_ALERT "Modulo ya cargado\n");
	}
	return i;
}

/* Funcion de escritura de la entrada "selection" */
int write_proc_desactivation(struct file *filepointer, const char __user *buffer, unsigned long len, void *data)
{
	int i,length;
	if (len > NAME_CLIP_LEN) {
		i = NAME_CLIP_LEN;
	} else {
		i = len;
	}
	/* Leemos del usuario */
	if (copy_from_user(my_buffer, buffer, i)) {
		printk(KERN_ALERT "Imposible escribir\n");
		return -EFAULT;
	}
	// Damos formato a la cadena
	sscanf(my_buffer, "%s", clip_name);	
	// Quitamos el caracter '\n' en caso de haberlo
	if (clip_name != NULL) {
		length = strlen(clip_name);
		if (clip_name[length - 1] == '\n')
			clip_name[length - 1] = '\0';
	}	
	/* Comprobamos que el modulo esté ya cargado, si no hemos acabado */
	if (find_module_in_list(&clip_name) == 0) {
		/* Ejecutamos la instruccion con el user mode helper */
		remove_module_custom(&clip_name);
	} else {
		printk(KERN_ALERT "Modulo no cargado\n");
	}
	return i;
}

/* Funcion de escritura vacia para evitar mensajes de error */
int write_proc_empty(struct file *filepointer, const char __user *buffer, unsigned long len, void *data) 
{
	return 0;
}
/**************************** FIN FUNCIONES LECTURA/ESCRITURA **********************************/
 
/**************************** INICIO DE FUNCIONES DE MANEJO DE LISTA ***************************/
/* Funcion para buscar un modulo en la lista */
int find_module_in_list(char **clip_name)
{
	struct list_head *pos;
	struct clipstruct *obj;
	list_for_each(pos, &list_clipstruct) {
		obj = list_entry(pos, struct clipstruct, list);
		// Si el modulo ya esta cargado lo notificamos
		if (strcmp(*clip_name, obj->name) == 0) {
			return 0;
		}
	}	
	return -1;
}
/**************************** FIN DE FUNCIONES DE MANEJO DE LISTA ******************************/

/**************************** INICIO DE FUNCIONES DE USERMODEHELPER*****************************/
/* Funcion para añadir un nuevo clipboard a la lista y lo carga con usermodehelper */
void add_module_custom(char **clip_name) 
{
	char *argv[] = {"/sbin/modprobe", NAME_CLIP, "-o", *clip_name, NULL};
	struct clipstruct *obj;	

	printk(KERN_ALERT "%s : %s : %s : %s : %s \n", argv[0],argv[1],argv[2],argv[3],argv[4]);
	if (call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC) != 0) {
		printk(KERN_ALERT "No se puede ejecutar el modulo\n");
	} else {
		obj = (struct clipstruct *) vmalloc( sizeof(struct clipstruct));
		obj->name = (char *)vmalloc(sizeof(char)*strlen(*clip_name));
		strcpy(obj->name, *clip_name);
		list_add(&obj->list, &list_clipstruct);	
		// Indicamos que hay un nuevo modulo
		num_clips++;
	}
}

/* Elimina el modulo indicado */
void remove_module_custom(char **clip_name)
{
	struct list_head *pos, *q;
	struct clipstruct *obj;
	// Argumentos de user mode helper
	char *argv[] = {"/sbin/rmmod", *clip_name, NULL};	
	
	// Si no hay clips, salimos
	if (num_clips == 0) {
		return;
	}
	// Recorremos la lista de modulos activos
	list_for_each_safe(pos, q, &list_clipstruct) {
		obj = list_entry(pos, struct clipstruct, list);
		// Si el nombre coincide con en el que buscamos, lo desinstalamos
		// Si el nombre es NULL, desinstalamos todos
		if (strcmp(obj->name, *clip_name) == 0) {
			// Lo descargamos con el user mode helper
			if (call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC) != 0) {
				printk(KERN_ALERT "No se puede descargar el modulo %s\n", *clip_name);
			} else {
				// Lo eliminamos de la lista
				list_del(pos);
				printk(KERN_ALERT "Borrando Modulo: %s de la lista\n", *clip_name);
				// Liberamos la memoria
				vfree(obj->name);
				vfree(obj);
				// Indicamos que hay un modulo menos
				num_clips--;
				// En cuanto encontramos el modulo, salimos
			}
			break;
		}
	}
}
/**************************** FIN DE FUNCIONES DE USERMODEHELPER *******************************/

/* Funcion invocada al cargar el modulo */
int modulo_carga(void)
{	
	// Inicialmente hay 0 clipboards
	num_clips = 0;
	// Reservamos memoria
	my_buffer = (char *) vmalloc(sizeof(char)*NAME_CLIP_LEN);
	clip_name = (char *) vmalloc(sizeof(char)*NAME_CLIP_LEN);	

	// Creamos el directorio PROC_DIR
	if (create_proc_dir_custom(PROC_DIR, &proc_dir_aiso, &fake_parent) == -ENOMEM) return -ENOMEM;
	
	// Creamos la entrada ACTIVATION
	if (create_proc_entry_custom(ACTIVATION, &proc_activation, &proc_dir_aiso) == -ENOMEM) return -ENOMEM;
	// Asociamos funciones de lectura y escritura y damos permisos
	proc_activation->read_proc			= read_proc_empty;
	proc_activation->write_proc 		= write_proc_activation;
	proc_activation->mode 				= S_IFREG | S_IRUGO;
	
	// Creamos la entrada DESACTIVATION
	if (create_proc_entry_custom(DESACTIVATION, &proc_desactivation, &proc_dir_aiso) == -ENOMEM) return -ENOMEM;
	// Asociamos funciones de lectura y escritura y damos permisos
	proc_desactivation->read_proc		= read_proc_empty;
	proc_desactivation->write_proc 		= write_proc_desactivation;
	proc_desactivation->mode 			= S_IFREG | S_IRUGO;
	
	// Creamos la entrada MONITOR
	if (create_proc_entry_custom(MONITOR, &proc_monitor, &proc_dir_aiso) == -ENOMEM) return -ENOMEM;
	// Asociamos funciones de lectura y escritura y damos permisos
	proc_monitor->read_proc			= read_proc_monitor;
	proc_monitor->write_proc 		= write_proc_empty;
	proc_monitor->mode 				= S_IFREG | S_IRUGO;

	return 0;
}

/* Funcion invocada al descargar el modulo */
void modulo_descarga(void) 
{
	// Eliminamos la entrada ACTIVATION
	remove_proc_entry_custom(ACTIVATION, &proc_activation, &proc_dir_aiso);
	
	// Eliminamos la entrada DESACTIVATION
	remove_proc_entry_custom(DESACTIVATION, &proc_desactivation, &proc_dir_aiso);
	
	// Eliminamos la entrada MONITOR
	remove_proc_entry_custom(MONITOR, &proc_monitor, &proc_dir_aiso);
	
	// Eliminamos el directorio CLIPBOARD
	remove_proc_dir_custom(PROC_DIR, &proc_dir_aiso, &fake_parent);

	// Liberamos la memoria
	vfree(clip_name);
	vfree(my_buffer);	
}

/* Funciones init y exit */
module_init(modulo_carga);
module_exit(modulo_descarga);